﻿
Public Module _debug

    Public Function callstack(Optional ByVal removeblanks As Boolean = False, _
                              Optional ByVal separator As Char = character.colon) As String
#If Not PocketPC AndAlso Not Smartphone Then
        Dim rtn As String = Nothing
        rtn = System.Environment.StackTrace()
        If removeblanks Then
            rtn = strrplc(rtn, "   ", emptyString)
            rtn = strrplc(rtn, newline.incode(), separator)
        End If
        Return rtn
#Else
        Return "##CANNOTTRACE##"
#End If
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function callingcode(Optional ByVal additionalJump As Int32 = 0) As String
#If Not (PocketPC OrElse Smartphone) Then
        Dim rtn As Text.StringBuilder = Nothing
        rtn = New Text.StringBuilder()
        Dim callStack As StackFrame = Nothing
        Dim jump As Int32 = 1

        jump += additionalJump
        callStack = New StackFrame(jump, True)
        If isemptystring(callStack.GetFileName()) Then
            rtn.Append("##MISSING_PDB##")
        Else
            rtn.Append(callStack.GetFileName())
            rtn.Append(character.leftBracket)
            rtn.Append(Convert.ToString(callStack.GetFileLineNumber()))
            rtn.Append(character.rightBracket)
        End If
        rtn.Append(character.colon)
        rtn.Append(callStack.GetMethod.Module.Name())
        rtn.Append(character.dot)
        rtn.Append(callStack.GetMethod.DeclaringType.FullName())
        rtn.Append(character.dot)
        rtn.Append(callStack.GetMethod.Name())

        Return Convert.ToString(rtn)
#Else
        Return "##CANNOTTRACE##"
#End If
    End Function

    Public Function invokingcode(Optional ByVal additionalJump As Int32 = 0) As String
        Return callingcode(additionalJump + 1)
    End Function

    'always called by debugassert & assertImpl, so the additionaljump is 1 as default value
    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Private Sub assertRaiseError(ByVal assertlevel As String, Optional ByVal msg As String = Nothing, _
                                 Optional ByVal additionaljump As Int32 = 1)
        If msg Is Nothing Then
            msg = assertlevel + " failed."
        Else
            msg = assertlevel + " failed, " + msg
        End If
        msg += newline.incode() + callstack()
        raiseError(msg, errorHandle.errorType.critical, additionaljump:=additionaljump + 1)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function debugassert(ByVal v As Boolean, Optional ByVal msg As String = Nothing) As Boolean
        If isdebugmode() Then
            Return assertImpl(v, msg, 1)
        Else
            If Not v Then
                assertRaiseError("debugassert", msg, 1)
                Return False
            Else
                Return True
            End If
        End If
    End Function

    Private PAUSEWHENINDEBUGMODE As Boolean = True

    Public Sub setstrongassert()
        PAUSEWHENINDEBUGMODE = False
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function assert(ByVal v As Boolean, Optional ByVal msg As String = Nothing) As Boolean
        Return assertImpl(v, msg, 1)
    End Function

    'always called by assert & debugassert, so additionaljump default to 1
    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Private Function assertImpl(ByVal v As Boolean, Optional ByVal msg As String = Nothing, _
                                Optional ByVal additionaljump As Int32 = 1) As Boolean
        If Not v Then
            assertRaiseError("assert", msg, additionaljump + 1)
            If isdebugmode() AndAlso PAUSEWHENINDEBUGMODE Then
                If errorHandle.mode = errorHandle.displayMode.console Then
                    pause()
                End If
            Else
                finishmainBase()
                forceQuit()
            End If

            Return False
        Else
            Return True
        End If
    End Function

    Private INDEBUGMODE As Boolean = False

    Public Sub setdebugmode()
        INDEBUGMODE = True
    End Sub

    Public Sub setnormalmode()
        INDEBUGMODE = False
    End Sub

    Public Function isreleasebuild() As Boolean
        Return Not isdebugbuild()
    End Function

    Public Function isdebugbuild() As Boolean
#If DEBUG Then
        Return True
#Else
        Return False
#End If
    End Function

    Public Function isdebugmode() As Boolean
#If DEBUG Then
        Return True
#Else
        Return INDEBUGMODE 
#End If
    End Function

    Public Sub debugpause(Optional ByVal needKeyInput As Boolean = True)
        If isdebugmode() Then
            If needKeyInput Then
                pause()
            Else
                Dim i As Int32
                Console.Write("wait 30 seconds")
                For i = 0 To 29
                    sleep(1000)
                    Console.Write(".")
                Next
                Console.Write(character.return)
            End If
        End If
    End Sub

    Public Sub pause(Optional ByVal showMessage As Boolean = True)
        On Error Resume Next
        If showMessage Then
            Console.Error.Write("press any key to continue")
        End If

        Dim c As Char
#If Not (PocketPC OrElse Smartphone) Then
        c = Console.ReadKey().KeyChar()
#Else
        c = Convert.ToChar(Console.In.Read())
#End If
        If showMessage Then
            Console.Error.Write(character.return)
        End If
    End Sub

    Sub New()
        'dirty hack here, for vs release/debug build
#If Not (PocketPC OrElse Smartphone) Then
        If strsame(envs.executingDirectory(), "release", False) OrElse _
           strsame(envs.executingDirectory(), "debug", False) Then
            setdebugmode()
        End If
#End If
    End Sub

End Module
